﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Windows.Threading;

namespace iStudio.Structure.Wave
{
    public class SafeObservableStatic
    {
        public static Dispatcher Dispatcher
        {
            set;
            get;
        }
    }

    public class WaveCollection<T> : IList<T>, INotifyCollectionChanged
    {
        private readonly IList<T> _frameNew;
        private readonly IList<T> _frameOld;

        private readonly IList<T> _collection;
        private readonly Dispatcher _dispatcher;
        private readonly ReaderWriterLock _sync;

        private int _frameSize;
        private int _frameIndex;

        public WaveCollection ()
        {
            _dispatcher = SafeObservableStatic.Dispatcher;

            _collection = new List<T> ();
            _sync = new ReaderWriterLock ();

            _frameNew = new List<T> ();
            _frameOld = new List<T> ();
        }

        public WaveCollection (IEnumerable<T> collection) : this()
        {
            _collection = new List<T> (collection);
        }

        #region IList<T> Members

        public void Add (T item)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                DoAdd (item);
            }
            else
            {
                _dispatcher.Invoke ((Action) (() => DoAdd (item)));
            }
        }

        public void Clear ()
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                DoClear ();
            }
            else
            {
                _dispatcher.Invoke ((Action) (DoClear));
            }
        }

        public bool Contains (T item)
        {
            _sync.AcquireReaderLock (Timeout.Infinite);
            bool result = _collection.Contains (item);
            _sync.ReleaseReaderLock ();
            return result;
        }

        public void CopyTo (T[] array, int arrayIndex)
        {
            _sync.AcquireReaderLock (Timeout.Infinite);
            _collection.CopyTo (array, arrayIndex);
            _sync.ReleaseReaderLock ();
        }

        public int Count
        {
            get
            {
                _sync.AcquireReaderLock (Timeout.Infinite);
                int result = _collection.Count;
                _sync.ReleaseReaderLock ();
                return result;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return _collection.IsReadOnly;
            }
        }

        public bool Remove (T item)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                return DoRemove (item);
            }
            return (bool) _dispatcher.Invoke (new Func<T, bool> (DoRemove), item);
        }

        public IEnumerator<T> GetEnumerator ()
        {
            return _collection.GetEnumerator ();
        }

        IEnumerator IEnumerable.GetEnumerator ()
        {
            return _collection.GetEnumerator ();
        }

        public int IndexOf (T item)
        {
            _sync.AcquireReaderLock (Timeout.Infinite);
            int result = _collection.IndexOf (item);
            _sync.ReleaseReaderLock ();
            return result;
        }

        public void Insert (int index, T item)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                DoInsert (index, item);
            }
            else
            {
                _dispatcher.Invoke ((Action) (() => DoInsert (index, item)));
            }
        }

        public void RemoveAt (int index)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                DoRemoveAt (index);
            }
            else
            {
                _dispatcher.Invoke ((Action) (() => DoRemoveAt (index)));
            }
        }

        public T this [int index]
        {
            get
            {
                //_sync.AcquireReaderLock (Timeout.Infinite);
                T result = _collection[index];
                //_sync.ReleaseReaderLock ();
                return result;
            }
            set
            {
                _sync.AcquireWriterLock (Timeout.Infinite);

                if (_collection.Count == 0 || _collection.Count <= index)
                {
                    _sync.ReleaseWriterLock ();
                    return;
                }

                T oldValue = _collection[index];

                _collection[index] = value;

                _frameNew.Add(_collection[index]);
                _frameOld.Add(oldValue);

                if ( (_frameNew.Count >= _frameSize) || (_frameSize <= 0) )
                {
                    //
                    // Remember frame index
                    //

                    _frameIndex = index;

                    CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, _frameNew, _frameOld, _frameIndex - (_frameNew.Count - 1)));

                    _frameNew.Clear();
                    _frameOld.Clear();
                }

                _sync.ReleaseWriterLock();
            }
        }

        #endregion

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        public void AddRotate (T item, int max)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                DoAddRotate (item, max);
            }
            else
            {
                _dispatcher.Invoke ((Action) (() => DoAddRotate (item, max)));
            }
        }

        private void DoAddRotate (T item, int max)
        {
            T removed = default(T);
            _sync.AcquireWriterLock (Timeout.Infinite);
            _collection.Add (item);

            if (_collection.Count > max && max > 0)
            {
                removed = _collection[0];
                _collection.RemoveAt (0);
            }
            _sync.ReleaseWriterLock ();

            // ReSharper disable CompareNonConstrainedGenericWithNull
            if ( (removed != null) && (CollectionChanged != null) )
            // ReSharper restore CompareNonConstrainedGenericWithNull
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, removed, 0));
            }
            if (CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item));
            }
        }

        public bool AddUnique (T item)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                return DoAddUnique (item);
            }
            return (bool) _dispatcher.Invoke (new AddDele (DoAddUnique), item);
        }

        private bool DoAddUnique (T item)
        {
            bool added = false;
            _sync.AcquireWriterLock (Timeout.Infinite);
            if (!_collection.Contains (item))
            {
                _collection.Add (item);
                added = true;
            }
            _sync.ReleaseWriterLock ();
            if (added && CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item));
            }
            return added;
        }

        public void ReleaseNotifyFrame ()
        {
            //
            // Release any buffered frames
            //

            if (_frameNew.Count > 0)
            {
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _frameNew, _frameOld, _frameIndex + 1));

                _frameNew.Clear();
                _frameOld.Clear();
            }
        }

        public void SetFrameChange(int frame)
        {
            _sync.AcquireWriterLock(Timeout.Infinite);

            ReleaseNotifyFrame ();

            _frameSize = frame;

            _sync.ReleaseWriterLock ();
        }

        public void Lock ()
        {
            _sync.AcquireWriterLock (Timeout.Infinite);
        }

        public void Unlock ()
        {
            _sync.ReleaseWriterLock ();
        }

        public List<T> ToList ()
        {
            _sync.AcquireReaderLock (Timeout.Infinite);
            List<T> list = _collection.ToList ();
            _sync.ReleaseReaderLock ();
            return list;
        }

        public T[] ToArray ()
        {
            _sync.AcquireReaderLock(Timeout.Infinite);
            T[] array = _collection.ToArray ();
            _sync.ReleaseReaderLock();

            return array;
        }

        private void DoAdd (T item)
        {
            _sync.AcquireWriterLock (Timeout.Infinite);
            _collection.Add (item);
            _sync.ReleaseWriterLock ();

            if (CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item));
            }
        }

        private void DoClear ()
        {
            _sync.AcquireWriterLock (Timeout.Infinite);
            _collection.Clear ();
            _sync.ReleaseWriterLock ();

            if (CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset));
            }
        }

        public bool Remove (IList items)
        {
            if (Thread.CurrentThread == _dispatcher.Thread)
            {
                _sync.AcquireWriterLock (Timeout.Infinite);
                foreach (object item in items)
                {
                    if (item is T)
                    {
                        if (_collection.Contains ((T) item))
                        {
                            _collection.Remove ((T) item);
                        }
                    }
                }
                _sync.ReleaseWriterLock ();
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset));
                return true;
            }

            return (bool) _dispatcher.Invoke (new Func<T, bool> (DoRemove), items);
        }

        private bool DoRemove (T item)
        {
            _sync.AcquireWriterLock (Timeout.Infinite);
            int index = _collection.IndexOf (item);

            if (index == -1)
            {
                _sync.ReleaseWriterLock ();
                return false;
            }

            bool result = _collection.Remove (item);
            _sync.ReleaseWriterLock ();

            if (result && CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, item, index));
            }
            return result;
        }

        private void DoInsert (int index, T item)
        {
            _sync.AcquireWriterLock (Timeout.Infinite);
            _collection.Insert (index, item);
            _sync.ReleaseWriterLock ();

            if (CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item, index));
            }
        }

        private void DoRemoveAt (int index)
        {
            _sync.AcquireWriterLock (Timeout.Infinite);

            if (_collection.Count == 0 || _collection.Count <= index)
            {
                _sync.ReleaseWriterLock ();
                return;
            }

            T item = _collection[index];
            _collection.RemoveAt (index);
            _sync.ReleaseWriterLock ();

            if (index < 0)
            {
                int asd = 123 +123;
            }

            if (CollectionChanged != null)
            {
                CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Remove, item, index));
            }
        }

        #region Nested type: AddDele

        private delegate bool AddDele (T item);

        #endregion
    }
}